Lektion 35



Ich möchte damit beginnen, Ihnen zu sagen, dass ich sehr stolz auf dieses Tutorial bin. Als ich das erste Mal die Idee hatte einen AVI Player in OpenGL zu programmieren, muss ich mich bei Jonathan de Blok bedanken, da ich keine Idee hatte, wie ich einen AVI Player programmieren sollte. Ich fing damit an meine Sammlung an Programmierbüchern durchzugehen. Nicht eins hatte was über AVI-Dateien enthalten. Ich habe dann alles über das AVI Format gelesen, was die MSDN hergab. Viele nützliche Informationen in der MSDN, aber ich benötigte mehr Informationen.

Nachdem ich das Netz studenlang nach AVI-Beispielen durchsucht hatte, hatte ich nur zwei Seiten gebookmarked. Ich möchte nicht sagen, dass meine Suchmaschinen-Fähigkeiten herausragend sind, aber in 99,9% aller Fälle habe ich kein Problem, das zu finden, was ich suche. Ich war absolut geschockt, als ich realisierte, wie wenig AVI Beispiele es gab! Die meisten Beispiele, die ich gefunden habe, liesen sich nicht kompilieren... Eine Handvoll von denen war viel zu komplex (zumindest für mich) und der Rest tat zwar ihre Arbeit, war aber in VB, Delphi, etc. programmiert (nicht VC++).

Die erste Seite, die ich gespeichert hattee, war ein Artikel, geschrieben von Jonathan Nix, mit dem Titel "AVI Files". Sie können sich diesen unter http://www.gamedev.net/reference/programming/features/avifile/ anschauen. Großen Respekt für Jonathan dafür das er ein extrem brilliantes Dokument über das AVI Format geschrieben hat. Obwohl ich mich entschieden hatte, einige Sachen anders zu machen, machten seine Beispiels-Code-Schnippsel und klaren Kommentare den Lernprozess um ein vielfaches leichter! Die zweite Seite trägt den Namen "The AVI Overview" von John F. McGown, Ph.D.. Ich könnte stundenlang davon schwärmen, wie fantastisch John's Seite ist, aber es ist einfacher, wenn Sie sich das selbst anschauen! Die URL ist http://www.jmcgowan.com/avi.html. Seine Seite deckt so ziemlich alles ab, was es über das AVI-Format zu wissen gibt! Dank an John, dass er eine solch wertvolle Seite der Öffentlichkeit zur Verfügung stellt.

Das letzte was ich noch erwähnen möchte, ist, dass KEIN Code abgeschrieben wurde und auch kein Code kopiert wurde. Der Code wurde innerhalb von 3 Tagen mit Hilfe der oben genannten Seiten und Artikel geschrieben. Das gesagt, denke ich, ist es noch wichtig anzumerken, dass mein Code vielleicht NICHT der beste Weg ist, eine AVI Datei abzuspielen. Es mag vielleicht nicht mal der richtige Weg sein, um ein AVI abzuspielen, aber es funktioniert und ist einfach zu verwenden! Wenn Sie den Code oder mein Coding-Style nicht mögen oder Sie denken, dass ich der Programmier-Community damit weh tue, indem ich dieses Tutorial veröffentliche, dann haben Sie ein paar Möglichkeiten: 1) Durchsuchen Sie das Netz für alternative Ressourcen 2) Schreiben Sie ihren eigenen AVI Player ODER 3) schreiben Sie ein besseres Tutorial! Jeder der diese Seite besucht, sollte mittlerweile wissen, dass ich ein durchschnittlicher Programmierer mit durchschnittlichen Fähigkeiten bin (ich habe darauf auf unzähligen Pages auf dieser Seite hingewiesen)! Ich programmiere zum SPASS! Das Ziel dieser Seite ist es, das Leben der nicht-Elite Programmierer einfacher zu machen, mit OpenGL zu beginnen. Die Tutorials sind bloss Beispiele wie 'ich' es geschafft habe, einen bestimmten Effekt zu erreichen... Nicht mehr, nicht weniger!

Auf zum Code...

Das erste was Sie bemerken werden, ist, dass wir die Videor For Windows Header / Library inkludieren und linken. Großen Dank an Microsoft (Ich kann's nicht glauben, dass ich das gerade gesagt habe!). Diese Library macht es ein Leichtes AVI Dateien zu öffnen und abzuspielen! Bis jetzt... müssen Sie nur wissen, dass Sie den vfw.h Header inkludieren MÜSSEN und Sie müssen die vfw32.lib Library Datei linken, wenn Sie den Code kompilieren wollen :)

#include < windows.h>							// Header Datei für Windows
#include < gl\gl.h>							// Header Datei für die OpenGL32 Library
#include < gl\glu.h>							// Header Datei für die  GLu32 Library
#include < vfw.h>							// Header Datei für Video For Windows
#include "NeHeGL.h"							// Header Datei für NeHeGL

#pragma comment( lib, "opengl32.lib" )					// Suche nach OpenGL32.lib während des Linkens
#pragma comment( lib, "glu32.lib" )					// Suche nach GLu32.lib während des Linkens
#pragma comment( lib, "vfw32.lib" )					// Suche nach VFW32.lib während des Linkens

#ifndef CDS_FULLSCREEN							// CDS_FULLSCREEN ist von einigen
#define CDS_FULLSCREEN 4						// Compiler nicht definiert. Indem wir das hier definieren
#endif									// können wir Fehler vermeiden

GL_Window*	g_window;
Keys*		g_keys;

Nun definieren wir unsere Variablen. angle wird dazu verwendet, unsere Objekte basierend auf der vertstrichenen Zeit zu rotieren. Wir werden angle für alle Rotationen verwenden, um das ganze einfach zu halten.

next ist eine Integer Variable die dazu verwendet wird, um zu zählen, wieviel Zeit vergangen ist (in Millisekunden). Sie wird dazu verwendet, die Framerate in einer annehmbaren Geschwindigkeit zu halten. Mehr darüber später!

frame ist natürlich der aktuelle frame, den wir aus unserer Animation anzeigen wollen. Wir fangen mit 0 (dem ersten Frame) an. Ich denke, man kann ruhig davon ausgehen, dass wenn man es geschafft hat, das Video zu öffnen, dass es mindestens ein Frame der Animation HAT :)

effect ist der aktuelle Effekt der auf dem Screen gezigt wird (object: Würfel, Sphere, Zylinder, Nichts). env ist ein boolescher Wert. Wenn er gleich true ist, wird Environment Mapping eingeschaltet, wenn er false ist, dann wird kein Environment Mapping auf das Objekt angewandt. Wenn bg gleich true ist, werden Sie das Video als Fullscreen hinter dem Objekt sehen. Wenn es false ist, werden Sie nur das Objekt sehen (und es wird nichts im Hintergrund sein).

sp, ep und bp werden dazu verwendet, sicherzustellen, dass der Benutzer keine Taste gedrückt hält.

// Benutzerdefinierte Variablen
float		angle;							// Wird für Rotation verwendet
int		next;							// Wird für Animation verwendet
int		frame=0;						// Frame Counter
int		effect;							// aktueller Effekt
bool		sp;							// Leertaste gedrückt?
bool		env=TRUE;						// Environment Mapping (standardmäßig an)
bool		ep;							// 'E' gedrückt?
bool		bg=TRUE;						// Hintergrund (standardmäßig an)
bool		bp;							// 'B' gedrückt?

Die psi Struktur wird die Informationen über die AVI Datei später enthalten. pavi ist ein Zeiger auf ein Buffer, der das neue Stream Handle aufnimmt, sobald die AVI Datei geöffnet wurde. pgf ist ein Zeiger auf unser GetFrame Objekt. bmih wird später im Code dazu verwendet, um einen Frame einer Animation in ein Format zu konvertieren, wie wir es haben wollen (enthält die Bitmap Header Info, die beschreibt was wir wollen). lastframe wird die Nummer des letzten Frames in der AVI Animation enthalten. width und height werden die Dimensionen des AVI Streams enthalten und zu letzt.... pdata ist ein Zeiger auf die Bilddaten, die zurückgegeben werden, nachdem wir ein Frame der Animation aus dem AVI erhalten haben! mpf wird dazu verwendet, um zu berechnen, wie viele Millisekunden jeder Frame angezeigt wird. Mehr dazu später.

AVISTREAMINFO		psi;						// Zeiger auf eine Struktur, die die Stream Infos enthält
PAVISTREAM		pavi;						// Handle auf einen offenen Stream
PGETFRAME		pgf;						// Zeiger auf ein GetFrame Objekt
BITMAPINFOHEADER	bmih;						// Header Information für DrawDibDraw Decoding
long			lastframe;					// Letzter Frame des Streams
int			width;						// Video Breite
int			height;						// Video Höhe
char			*pdata;						// Zeiger auf die Texturdaten
int			mpf;						// wird die ungefähre Anzahl an Millisekunden pro Frame enthalten

In diesem Tutorial werden wir 2 verschiedene quadratische Formen (eine Sphere und ein Zylinder) mit der GLU Library erzeugen. quadratic ist ein Zeiger auf unser Quadric Objekt.

hdd ist ein Handle auf einen DrawDib Device Kontext. hdc ist ein Handle auf einen Device Kontext.

hBitmap ist ein Handle auf ein Device abhängiges Bitmap (wird später im Bitmap Konvertierungsprozess verwendet).

data ist ein Zeiger der eventuell auf unsere konvertierte Bitmap Imagedaten zeigen wird. Das wird später beim Code noch Sinn ergeben. Lesen Sie weiter :)

GLUquadricObj *quadratic;						// Speicherplatz für unsere Quadratic Objekte

HDRAWDIB hdd;								// Handle für unser Dib
HBITMAP hBitmap;							// Handle auf ein Device abhängiges Bitmap
HDC hdc = CreateCompatibleDC(0);					// erzeugt einen kompatiblen Device Context
unsigned char* data = 0;						// Zeiger auf unser in der Größe angepasstes Image

Nun zu etwas Assembler. An die unter Ihnen, die noch nie zuvor Assembler verwendet haben, seien Sie nicht eingeschüchtert. Es mag kryptisch aussehen, ist aber tatsächlich ziemlich einfach!

Während ich dieses Tutorial geschrieben habe, habe ich etwas sehr seltsames entdeckt. Das erste Video was ich wirklich mit diesem Code zum laufen bekommen haben, wurde zwar abgespielt, aber die Farben waren verhunzt. Alles was rot sein sollte, war blau und alles was blau sein sollte, war rot. Ich bin absolut VERRÜCKT geworden! Ich war davon überzeugt einen Fehler irgendwo im Code zu haben, aber ich konnte den Bug nicht finden! Deshalb habe ich wieder angefangen mich durch das MSDN zu lesen. Warum sollten die rot und blau Bytes vertauscht sein!?! Das MSDN sagte klar aus, dass 24 Bit Bitmaps RGB seien!!! Nach weiterem Lesen habe ich das Problem entdeckt. In WINDOWS sind RGB Daten rückwärts gespeichert (BGR). In OpenGL ist RGB tatsächlich was es sein soll... RGB!

Nach ein paar Beschwerden von Microsoft Fans :) habe ich mich entschieden eine kurze Anmerkung hinzuzufügen! Ich beschwere mich nicht über Windows, dass sie RGB-Daten rückwärts speichern. Ich finde es nur sehr frustrierend, dass es weiterhin RGB heißt, wenn in der Datei eigentlich BGR vorzufinden ist!

Blue fügt hinzu: Es hat mehr was mit "little Endian" und "big Endian" zu tun. Intel und Intel kompatible verwenden Little Endian, wo das am wenigsten signifikante Byte (LSB) als erstes gespeichert wird. OpenGL kommt von Silicon Graphics Maschinen, welche wahrscheinlich Big Endian sind und somit der OpenGL Standard das Bitmap Format im Big Endian Format benötigt. Ich denke, so ist es richtig.

Wunderbar! So, da war ich mit meinem Player der wie der letzte Dreck aussieht! Meine erste Lösung war es, die Bytes manuell mit einer for next Schleife zu tauschen. Es funktionierte, aber war sehr langsam. Komplett genervt modifizierte ich den Textur-Erzeugungs Code, damit GL_BGR_EXT anstatt GL_RGB benutzt wird. Eine großer Geschwindigkeitszuwachs und die Farbe sahen gut aus. So, mein Problem war gelöst... dachte ich zumindest! Es stellte sich heraus, dass einige OpenGL Treiber Probleme mit GL_BGR_EXT haben.... Und wieder am Anfang :(

Nachdem ich mit meinem guten Freund Maxwell Sayles gesprochen hatte, riet er mir, dass ich die Bytes mittels Assembler Code tauschen sollte. Eine Minute später hat er mit den folgenden Code über ICQ geschickt! Er mag nicht optimiert sein, aber er ist schnell und erfüllt seine Aufgabe!

Jeder Frame der Animation wird in einem Buffer gespeichert. Das Image wird immer 256 Pixel breit sein, 256 Pixel hoch und 1 Byte pro Farbe haben (3 Bytes pro Pixel). Der folgende Code geht durch den Buffer und tauscht die Rot und Blau Bytes. Rot wird an ebx+0 gespeichert und blau bei ebx+2. Wir bewegen uns immer um 3 Bytes durch den Buffer (da ein Pixel aus 3 Bytes besteht). Wir iterieren durch die Daten, bis alle Bytes getauscht wurden.

Ein paar von euch waren über den Gebrauch von Assembler Code ziemlich unglücklich, weshalb ich dachte, dass ich erkläre warum ich ihn hier in diesem Tutorial verwende. Ursprünglich hatte ich, wie ich bereits erwähnte, geplant GL_BGR_EXT zu verwenden, es funktioniert. Aber nicht auf allen Karten! Ich habe mich dann entschieden die Swap Methode aus dem letzten Tutorial zu verwenden (sehr sauberer XOR Tausch-Code). Der Tausch-Code funktioniert auf allen Maschinen, ist aber nicht sonderlich schnell. Im letzten Tutorial, ja, funktionierte es GROßARTIG. In diesem Tutorial arbeiten wir mit REAL-TIME Video. Sie wollen den schnellsten Tausch, den Sie kriegen können. Nach dem Abwägen der Möglichkeiten, denke ich, dass ASM die beste Wahl ist! Wenn Sie eine bessere Lösung haben, bitte... VERWENDEN SIE SIE! Ich erzähle Ihnen nicht, wie Sie die Sachen machen MÜSSEN. Ich zeige Ihnen, wie ich es gemacht habe. Ich erkläre außerdem detailiert, was der Code macht. Auf diese Weise können Sie den Code durch etwas besseres ersetzen, da Sie genau wissen, was der Code macht und somit ist es einfacher eine alternative Lösung zu finden, wenn Sie ihren eigenen Code schreiben wollen!

void flipIt(void* buffer)						// tauscht die Rot und Blau Bytes (256x256)
{
	void* b = buffer;						// Zeiger auf den Buffer
	__asm								// es folgt Assembler Code
	{
		mov ecx, 256*256					// Initialisiere einen Zähler (Dimension des Speicherblocks)
		mov ebx, b						// ebx soll auf unsere Daten (b) zeigen
		label:							// Label das für die Schleife verwendet wird
			mov al,[ebx+0]					// Lade den Wert an ebx nachal
			mov ah,[ebx+2]					// Lade den Wert an ebx+2 nach ah
			mov [ebx+2],al					// Speichere Wert in al an ebx+2
			mov [ebx+0],ah					// Speichere Wert in ah an ebx

			add ebx,3					// Bewege 3 Bytes weiter in den Daten
			dec ecx						// dekrementiert unseren Schleifenzähler
			jnz label					// wenn noch nicht null dann springe zurück zum Label
	}
}

Der folgende Code öffnet die AVI-Datei im Lese-Modus. szFile ist der Name der Datei, die wir öffnen wollen. title[100] wird verwendet um den Titel des Fenster zu ändern (um Informationen über die AVI-Datei anzuzeigen).

Als erstes müssen wir AVIFileInit() aufrufen. Damit initialisieren wir die AVI-Datei Library (damit wir dann auch anfangen können).

Es gibt viele Wege eine AVI-Datei zu öffnen. Ich habe mich für AVIStreamOpenFromFile(...) entschieden. Damit wird ein einzelner Stream aus einer AVI-Datei geöffnet (AVI Dateien können mehrere Streams enthalten).

Die Parameter sind wie folgt: pavi ist ein Zeiger auf den Buffer, der das neue Stream Handle aufnimmt. szFile ist natürlich der Name der AVI-Datei, die wir öffnen wollen (komplett mit Pfad). Der dritte Parameter ist die Art des Streams, die wir öffnen wollen. In diesem Projekt ist nur der VIDEO Stream interessant (streamtypeVIDEO). Der vierte Parameter ist gleich ß. Das bedeutet, dass wir den ersten streamtypeVIDEO haben wollen (es können mehrere Video Streams in einer einzelnen AVI-Datei seien... Wir wollen den ersten Stream). OF_READ bedeutet, dass wir die Datei NUR zum lesen öffnen wollen. Der letzte Parameter ist ein Zeiger auf einen Class Identifizierer des Handlers den Sie verwenden wollen. Um ehrlich zu sein: ich habe keine Idee was das macht. Ich habe Windows das für mich auswählen lassen, indem ich NULL als letzten Parameter übergeben habe!

Wenn irgendwelche Fehler beim Öffnen der Datei aufgetreten sind, erscheint eine Message Box, die Sie wissen lässt, dass der Stream nicht geöffnet werden konnte. Ich gebe kein PASS oder FAIL dem Aufrufer-Code zurück, wenn es also fehl schlägt, versucht das Programm weiterzulaufen. Etwas Fehler-Checking einzufügen sollte nicht so schwierig sein, ich war einfach zu faul :)

void OpenAVI(LPCSTR szFile)						// Öffnet eine AVI-Datei(szFile)
{
	TCHAR	title[100];						// wird den modifizierten Fenster-Titel enthalten

	AVIFileInit();							// Öffnet die AVIFile Library

	// Öffnet den AVI Stream
	if (AVIStreamOpenFromFile(&pavi, szFile, streamtypeVIDEO, 0, OF_READ, NULL) !=0)
	{
		// Ein Fehler ist beim Öffnen des Streams aufgetreten
		MessageBox (HWND_DESKTOP, "Failed To Open The AVI Stream", "Error", MB_OK | MB_ICONEXCLAMATION);
	}

Wenn wir bis hier gekommen sind, ist es sicher anzunehmen, dass die Datei geöffnet wurde und ein Stream gefunden wurde! Als nächstes holen wir uns mit AVIStreamInfo(...) ein paar Informationen über die AVI-Datei.

Vorher haben wir eine Struktur namens psi erzeugt, welche Informationen über den AVI-Stream enthalten wird. Wir füllen diese Struktur mit Information über das AVI mit der ersten folgenden Codezeile. Alles, von der Breite des Streams (in Pixeln) bis zur Framerate der Animation, wird in psi gespeichert. Für jene unter Ihnen, die eine annehmbare Abspiel-Geschwindigkeit haben wollen, beachten was ich gerade erzählt habe. Für mehr Informationen schauen Sie AVIStreamInfo im MSDN nach.

Wir können die Breite eines Frames errechnen, indem wir den linken Rand vom rechte Rand subtrahieren. Das Ergebniss sollte ein genaues Ergebniss der Breite in Pixeln sein. Für die Höhe subtrahieren wir die Oberkante des Frames von der Unterkante des Frames. Dadurch erhalten wir die Höhe in Pixeln.

Wir ermitteln dann die letzte Frame-Nummer aus der AVI-Datei indem wir AVIStreamLength(...) verwenden. Dies gibt uns die Anzahl der Frames einer Animation in einer AVI-Datei zurück. Das Ergebniss wir in lastframe gespeichert.

Die Framerate zu berechnen ist ziemlich einfach. Frames pro Sekunde = psi.dwRate / psi.dwScale. Der zurückgegebene Wert sollte der Framerate entsprechen, wenn Sie einen Rechtsklick auf das AVI machen und sich seine Eigenschaften anschauen. Was hat das aber mit mpf zu tun werden Sie sich fragen. Als ich den Animations-Code geschrieben habe, habe ich zu erst versucht, die Frames pro Sekunde zu benutzen, um den korrekten Frame der Animation auszuwählen. Da trat allerdings ein Problem auf... Das Video wurde zu schnell abgespeielt! Ich schaute mir also die Video-Eigenschaften an. Die face2.avi Datei ist 3.36 Sekunden lang. Die Framerate ist 29,974 Frames pro Sekunde. Das Video hat 91 Frames für die Animation. Wenn Sie 3,36 mit 29,974 multiplizieren erhalten Sie 100 Frames für die Animation. Sehr eigenartig!

Deshalb entschied ich mich, das ganze etwas anders zu machen. Anstatt die Frames pro Sekunde zu berechnen, berechnete ich, wie lange jeder Frame angezeigt werden soll. AVIStreamSampleToTime() konvertiert eine Position in der Animation in die Anzahl der Millisekunden, die benötigt werden würden, um zu dieser Position zu kommen. Deshalb berechnen wir, wie lang das gesamte Video in Millisekunden ist, indem wir die Zeit (in Millisekunden) des letzten Frames (lastframe) ermitteln. Wir dividieren dann das Ergebniss durch die Gesamtanzahl der Frames der Animation (lastframe). Dadurch erhalten wir die Dauer der Zeit, die jeder Frame in Millisekunden angezeigt werden soll. Wir speichern das Ergebniss in mpf (Millisekunden pro Frame). Sie könnte die Millisekunden pro Frame auch berechnen, indem Sie die Dauer der Zeit für nur einen Frame der Animation mit folgenden Code ermitteln: AVIStreamSampleToTime(pavi,1). Beide Wege sollten funktionieren! Dank an Albert Chaulk für die Idee!

Der Grund warum ich die ungefähre Anzahl an Millisekunden pro Frame sage, ist, weil mpf ein Integer ist, so dass jeder Floating Wert gerundet wird.

	AVIStreamInfo(pavi, ψ, sizeof(psi));				// Liest Informationen über den Stream in psi ein
	width=psi.rcFrame.right-psi.rcFrame.left;			// Breite ist die rechte Seite des Frames minus links
	height=psi.rcFrame.bottom-psi.rcFrame.top;			// Höhe ist die untere Seite des Frames minus oben

	lastframe=AVIStreamLength(pavi);				// Der letzte Frame des Streams

	mpf=AVIStreamSampleToTime(pavi,lastframe)/lastframe;		// berechne die ungefähre Anzahl an Millisekunden pro Frame

Da OpenGL Texturdaten zur Basis 2 benötigt und da die meisten Videos 160x120, 320x240 oder irgendwelche anderen eigenartigen Dimensionen haben, benötigen wir eine schnelle Möglichkeit, dass Video 'on the fly' in ein Format zu verwandeln, dass wir als Textur verwenden können. Um das zu machen, nutzen wir den Vorteil bestimmter Windows Dib Funktionen.

Als erstes müssen wir die Art des Bildes beschreiben, dass wir haben wollen. Dafür füllen wir die bmih BitmapInfoHeader Struktur mit den erforderlichen Parametern. Wir fangen damit an, die Größe der Struktur zu setzen. Dann setzen wir die Bitplanes auf 1. 3 Bytes an Daten langen für 24 Bits (RGB). Wir wollen das Image 256 Pixel breit und 256 Pixel hoch haben und zu guter letzt wollen wir Daten als UNCOMPRESSED RGB Daten (BI_RGB) zurückgegeben bekommen.

CreateDIBSection erzeugt ein dib in welches wir direkt schreiben können. Wenn alles glatt läuft, zeigt hBitmap auf die dib's bit Werte. hdc ist ein handle auf ein Device Context (DC). Der zweite Parameter ist ein Zeiger auf unsere BitmapInfo Struktur. Die Struktur enthält Informationen über die dib Datei, die oben erwähnt wurde. Der dritte Parameter (DIB_RGB_COLORS) spezifiziert, dass die Daten RGB Werte sind, data ist ein Zeiger auf eine Variable die einen Zeiger auf den Platz aufnimmt, wo die DIB's Bit Werte gespeichert werden. (Hui, was für ein Satz). Indem wir den fünften Wert auf NULL setzen, wird Speicher für unser DIB alloziert. Zu guter letzt können wir den letzten Parameter ignorieren (auf NULL setzen).

Zitat aus dem MSDN: Die SelectObject Funktion wählt ein Objekt in einem spezifzierten Device Context (DC) aus.

Wir haben nun ein DIB erzeugt, in welches wir direkt zeichnen können. Yay :)

	bmih.biSize		= sizeof (BITMAPINFOHEADER);		// Größe von BitmapInfoHeader
	bmih.biPlanes		= 1;					// Bitplanes
	bmih.biBitCount		= 24;					// das Bits Format welches wir haben wollen (24 Bit, 3 Bytes)
	bmih.biWidth		= 256;					// gewünschte Breite (256 Pixel)
	bmih.biHeight		= 256;					// gewünschte Höhe (256 Pixel)
	bmih.biCompression	= BI_RGB;				// angeforderter Modus = RGB

	hBitmap = CreateDIBSection (hdc, (BITMAPINFO*)(&bmih), DIB_RGB_COLORS, (void**)(&data), NULL, NULL);
	SelectObject (hdc, hBitmap);					// wähle hBitmap in unserem Device Context (hdc) aus

Es müssen noch ein paar Dinge gemacht werden, bevor wir die Frames aus dem AVI auslesen können. Als nchstes müssen wir unser Video darauf vorbereiten, die Frames aus der AVI-Datei zu dekomprimieren. Wir machen das mit der AVIStreamGetFrameOpen(...) Funktion.

Wir können eine ähnliche Struktur wie die obige als zweiten Parameter übergeben, um ein bestimmtes Video-Format zurückgeliefert zu bekommen. Unglücklicherweise können Sie nur die Breite und Höhe des zurückzuliefernden Bildes beeinflusse. Das MSDN erwähnt auch, dass Sie AVIGETFRAMEF_BESTDISPLAYFMT übergeben können, um das beste Anzeige Format auszuwählen. Komischerweise hatte mein Compiler keine Definition dafür.

Wenn alles glatt läuft, wird ein GETFRAME Objekt zurückgegeben (welches wir benötigen, um Frames-Daten zu lesen). Wenn irgendwelche Probleme auftauchen, wird eine Message Box angezeigt, die Ihnen mitteilt, dass ein Fehler aufgetreten ist!

	pgf=AVIStreamGetFrameOpen(pavi, NULL);				// Erzeuge PGETFRAME unter der Benutzung des angeforderten Modus
	if (pgf==NULL)
	{
		// Ein Fehler ist beim Öffnen des Frames aufgetreten
		MessageBox (HWND_DESKTOP, "Failed To Open The AVI Frame", "Error", MB_OK | MB_ICONEXCLAMATION);
	}

Der folgende Code gibt die Breite, Höhe und Frames des Videos im Titel aus. Wir zeigen den Titel oben im Fenster mit dem Befehl SetWindowText(...) an. Lassen Sie das Programm im Fenster-Modus laufen, um zu sehen, was der folgende Code macht.

	// Informationen für die Titelleiste (Breite / Höhe / Letzter Frame)
	wsprintf (title, "NeHe's AVI Player: Width: %d, Height: %d, Frames: %d", width, height, lastframe);
	SetWindowText(g_window->hWnd, title);				// Ändere die Titelleiste
}

Nun zum spaßigen Teil... Wir ermitteln einen Frame aus dem AVI und konvertieren ihn in eine brauchbare Image Größe / Farbtiefe. lpbi wird die BitmapInfoHeader Informationen für den Frame der Animation enthalten. Wir erreichen mehrere Sachen auf einmal mit der folgenden zweiten Codezeile. Als erstes ermitteln wir einen Frame der Animation... Der Frame den wir haben wollen, wird durch frame spezifziert. Das holt den Frame der Animation und wird lpbi mit den Header-Informationen für diesen Frame füllen.

Nun zum spaßigen Teil... Wir müssen auf ein paar Bild-Daten zeigen. Dafür müssen wir die Header-Informationen überspringen (lpbi->biSize). Eine Sache, die ich nicht bemerkt habe, bis ich dieses Tutorial geschrieben habe, war, dass wir auch jegliche Farbinformationen überspringen müssen. Um das zu machen, addieren wir die benutzten Farben multipliziert mit der Größe von RGBQUAD (biClrUsed*sizeof(RGBQUAD)). Nachdem ALL das getan wurde :) erhalten wir einen Zeiger auf die Image-Daten (pdata).

Nun müssen wir den Frame der Animation in eine brauchbare Texturgröße konvertieren, sowie die Daten in RGB-Daten konvertieren. Um das zu machen, benutzen wir DrawDibDraw(...).

Eine kurze Erklärung. Wir könne direkt in unseren eigenen DIB zeichnen. Das ist das, was DrawDibDraw(...) macht. Der erste Parameter ist ein Handle auf unseren DrawDib DC. Der zweite Parameter ist ein Handle auf den DC. Als nächstes haben wir die obere linke Ecke (0,0) und die untere linke Ecke (256,256) des Ziel-Rechtecks.

lpbi ist ein Zeiger auf die bitmapinfoheader Information für den Frame den wir gerade eingelesen haben. pdata ist ein Zeiger auf die Image Daten für den Frame den wir eingelesen haben.

Dann haben wir die obere line Ecke (0,0) des Quell-Bildes (des Frames den wir gerade gelesen haben) und die untere rechte Ecke des Frames den wir gerade gelesen haben (Breite des Frames, Höhe des Frames). Der letzte Parameter sollte auf 0 belassen werden.

Dies wird ein Bild jeglicher Größe / Farbtiefe in ein 256*256*24bit Bild kovertieren.

void GrabAVIFrame(int frame)						// hole ein Frame aus demStream
{
	LPBITMAPINFOHEADER lpbi;					// enthält die Bitmap Header Informationen
	lpbi = (LPBITMAPINFOHEADER)AVIStreamGetFrame(pgf, frame);	// hole Daten aus dem AVI Stream
	pdata=(char *)lpbi+lpbi->biSize+lpbi->biClrUsed * sizeof(RGBQUAD);	// Zeiger auf die Daten, die von AVIStreamGetFrame zurückgegeben werden
										// (überspringe die Header Informationen, um an die Daten zu kommen)
	// konvertiere Daten in das geforderte Bitmap Format
	DrawDibDraw (hdd, hdc, 0, 0, 256, 256, lpbi, pdata, 0, 0, width, height, 0);

Wir haben unseren Frame der Animation, aber die roten und blauen Bytes sind vertauscht. Um dieses Problem zu lösen, springen wir zu unserem schnellen flipIt(...) Code. Erinnern Sie sich, data ist ein Zeiger auf eine Variable, die einen Zeiger auf die Location aufnimmt, die die DIB's Bit Werte enthält. Was das bedeutet, ist, dass nachdem wir DrawDibDraw aufrufen, wird data auf die in der Größe geänderten (256*256) / modifizierten (24 bit) Bitmap Daten zeigen.

Ursprünglich habe ich die Textur aktualisiert, indem ich sie bei jedem Frame der Animation neu erzeugt habe. Ich habe ein paar Email erhalten, wo vorgeschlagen wurde, dass ich glTexSubImage2D() benutzen sollte. Nachdem ich durch das OpenGL Red Book geblätter habe, bin ich über folgendes Zitat gestolpert: "Eine Textur zu erzeugen kann rechenintensiver sein, als eine bestehende zu modifizieren. In OpenGL Release 1.1 gibt es ein paar neue Routinen um Teile oder die ganze Textur mit neuen Informationen zu ersetzen. Das kann für bestimmte Applikationen nützlich sein, wie bei Real-Time aufgenommenen Video-Bildern als Textur-Bildern. Für diese Applikation macht es Sinn, eine einzelne Textur zu erzeugen und glTexSubImage2D() zu verwenden, um ständig die Textur-Daten mit neuen Video Bildern zu ersetzen".

Ich persönlich habe keine große Geschwindigkeitssteigerung festgestellt, aber Sie werde es vielleicht auf langsameren Karten! Die Parameter für glTexSubImage2D() sind wie folgt: Unser Ziel, welches eine 2D Textur ist (GL_TEXTURE_2D). Das Detail-Level (0) welches für Mipmapping benutzt wird. Den x (0) und y (0) Offset, was OpenGL mitteilt, wo mit dem Kopieren angefangen werden soll (0,0 ist die untere linke Ecke der Textur). Dann haben wir die Breite und Höhe des Bildes, welches wir kopieren wollen, was 256 Pixel breit und 256 Pixel hoch ist. GL_RGB ist das Format unserer Daten. Wir kopieren unsigned Bytes. Zu letzt... Der Zeiger auf unsere Daten wird durch data repräsentiert. Sehr einfach!

Kevin Rogers fügt hinzu: Ich wollte nur einen weiteren wichtigen Grund erwähnen, warum man glTexSubImage2D verwenden sollte. Nicht nur, dass es bei vielen OpenGL Implementationen schneller ist, sondern muss hier die Ziel-Fläche nicht mehr eine Dimension zur Basis 2 haben. Das ist besonders nützlich für das Abspielen von Videos, da die typischen Dimensionen für einen Frame selten zur Basis 2 sind (häufig etwas wie 320 x 200). Das gibt Ihnen die Flexibilität, einen Video-Stream in seinem original Seitenverhältnis abzuspielen, anstatt jeden Frame zu verzerren / zu clippen, damit es in die Texur-Dimensionen passt.

Es ist wichtig anzumerken, dass Sie eine Textur NICHT aktualisieren können, wenn Sie sie nicht zuerst erzeugt haben! Wir erzeugen die Textur in dem Initialize() Code!

Was ich ebenfalls erwähnen wollte... Wenn Sie planen, mehr als eine Textur in Ihrem Projekt zu verwenden, stellen Sie sicher, dass Sie die Textur binden, die Sie aktualisieren wollen. Wenn Sie die Textur nicht binden, aktualisieren Sie gegebenenfalls Texturen, die Sie nicht aktualisieren wollen!

	flipIt(data);							// Tausche die Roten und Blauen Bytes (GL Kompatabilität)

	// aktualisiere die Textur
	glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, 256, 256, GL_RGB, GL_UNSIGNED_BYTE, data);
}

Der folgende Codeabschnitt wird aufgerufen, wenn das Programm beendet wird. Wir schließen unser DrawDib DC und geben allozierte Ressourcen frei. Wir geben dann die AVI GetFrame Ressourcen frei. Zu letzt geben wir den Stream und dann die Datei frei.

void CloseAVI(void)							// Schliesse die AVI-Datei korrekt
{
	DeleteObject(hBitmap);						// Lösche das Device abhängige Bitmap Objekt
	DrawDibClose(hdd);						// Schliesst den DrawDib Device Context
	AVIStreamGetFrameClose(pgf);					// De-alloziert die GetFrame Resourcen
	AVIStreamRelease(pavi);						// Gibt den Stream frei
	AVIFileExit();							// Gibt die File frei
}

Die Initialisierung ist ziemlich einfach. Wir setzen den Winkel anfangs auf 0. Wir öffnen dann die DrawDib Library (welche den DC ermittelt). Wenn alles glatt läuft, enthält hdd ein Handle auf den neu erzeugten Device Kontext.

Unsere Löschfarbe für den Screen ist schwarz, Depth Testing aktiviert, etc.

Dann erzeugen wir einen neuen Quadric. quadratic ist ein Zeiger auf unser neues Objekt. Wir initialisieren 'smoothe' Normalenvektoren und aktivieren Koordinaten-Generation für unseren Quadric.

BOOL Initialize (GL_Window* window, Keys* keys)				// Jeglicher GL Init Code & Benutzer Initialisierungen kommen hier hin
{
	g_window	= window;
	g_keys		= keys;

	// Fange mit der Benutzer Initialisierung an
	angle = 0.0f;							// Setze Anfangs Winkel auf 0
	hdd = DrawDibOpen();						// hole ein Device Context für unser Dib
	glClearColor (0.0f, 0.0f, 0.0f, 0.5f);				// schwarzer Hintergrund
	glClearDepth (1.0f);						// Depth Buffer Setup
	glDepthFunc (GL_LEQUAL);					// Die Art des Depth Testing (kleiner oder gleich)
	glEnable(GL_DEPTH_TEST);					// aktiviere Depth Testing
	glShadeModel (GL_SMOOTH);					// wähle Smooth Shading aus
	glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);		// Setze genaueste perspektiven Berechnung auf 

	quadratic=gluNewQuadric();					// erzeuge einen Zeiger auf das Quadric Objekt
	gluQuadricNormals(quadratic, GLU_SMOOTH);			// erzeuge 'smoothe' Normalenvektoren
	gluQuadricTexture(quadratic, GL_TRUE);				// erzeuge Texturkoordinaten

Im nächsten Stück Code aktivieren wir 2D Texturmapping, setzen die Texturfilter auf GL_NEAREST (schnell, aber nicht so gut aussehend) und wir initialisieren Sphere Mapping (um den Environment Mapping Effekt zu erzeugen). Spielen Sie etwas mit den Filtern herum. Wenn Sie genügend Rechenleistung haben, versuchen Sie GL_LINEAR aus, um eine weicher aussehende Animation zu erhalten.

Nachdem wir unsere Textur und Sphere Mapping initialisiert haben, öffnen wir die .AVI-Datei. Ich habe versucht die Dinge einfach zu halten... wie Sie sehen können :) Die Datei, die wir öffnen werden, heißt face2.avi... sie liegt im data Verzeichnis.

Als letztes müssen wir unsere anfangs Textur erzeugen. Wir müssen das machen, um glTexSubImage2D() benutzen zu können, um unsere Textur in GrabAVIFrame() aktualisieren zu können.

	glEnable(GL_TEXTURE_2D);					// aktiviere Textur Mapping
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);// Setze Textur Max Filter
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);// Setze Textur Min Filter

	glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);		// Setze den Textur Generation Modus für S für's Sphere Mapping
	glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);		// Setze Textur Generation Modus für T für's Sphere Mapping

	OpenAVI("data/face2.avi");					// Öffne die AVI Datei

	// erzeuge die Textur
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

	return TRUE;							// gebe TRUE zurück (Initialisierung war erfolgreich)
}

Wenn wir beenden, rufen wir CloseAVI() auf. Damit sschließen wir die AVI-Datei korrekt und geben alle gbenutzen Ressourcen frei.

void Deinitialize (void)						// Jegliche Benutzer DeInitialisation kommt hier hin
{
	CloseAVI();							// Schliesst die AVI-Datei
}

Hier überprüfen wir auf Tastendrücke und aktualisieren unsere Rotation (angle) basierend auf der verstrichenen Zeit. Zur Zeit muss ich den Code noch nicht detailliert erklären. Wir überprüfen ob die Leertaste gedrückt wurde. Für den Fall das ja, inkrementieren wir den Effekt. Wir haben drei Effekte (Würfel, Sphere, Zylinder) und wenn der 4te Effekt ausgewählt ist (effect=3) wird nichts gezeichnet... und nur die Hintergrundszene wird gezeigt! Wenn wir beim 4ten Effekt sind und die Leertaste gedrückt wird, gehen wir zurück zum ersten Effekt (effect=0). Ja, ich weiß, ich hätte es OBJECT nennen sollen :)

Dann überprüfen wir, ob die 'B' Taste gedrückt wurde und wenn ja, wechseln wir den Hintergrund (bg) von AN nach AUS oder von AUS nach AN.

Environment Mapping wird auf die selbe Art erreicht. Wir überprüfen, ob 'E' gedrückt wurde. Wenn ja, wechseln wir env von TRUE auf FALSE oder von FALSE auf TRUE. Damit wird Environment Mapping aus- oder anangeschaltet!

Der Winkel (angle) wird bei jedem Update()-Aufruf um ein bißchen inkrementiert. Ich dividiere die verstrichene Zeit durch 60.0f, um die Dinge etwas langsamer laufen zu lassen.

void Update (DWORD milliseconds)					// führe Bewegungs-Aktualisierung hier aus
{
	if (g_keys->keyDown [VK_ESCAPE] == TRUE)			// wurde ESC gedrückt?
	{
		TerminateApplication (g_window);			// beende das Programm
	}

	if (g_keys->keyDown [VK_F1] == TRUE)				// wurde F1 gedrückt?
	{
		ToggleFullscreen (g_window);				// wechsel Fullscreen Modus
	}

	if ((g_keys->keyDown [' ']) && !sp)				// wurde die Leertaste gedrückt und wird nicht noch immer gehalten?
	{
		sp=TRUE;						// Setze sp auf True
		effect++;						// Ändere Effekt (inkrementiere effect)
		if (effect>3)						// über unserem Limit?
			effect=0;					// Resette zurück auf 0
	}

	if (!g_keys->keyDown[' '])					// wurde Leertaste losgelassen?
		sp=FALSE;						// Setze sp auf False

	if ((g_keys->keyDown ['B']) && !bp)				// wurde 'B' gedrückt und wird nicht mehr gehalten? 
	{
		bp=TRUE;						// Setze bp auf True
		bg=!bg;							// wechsel Hintergrund aus/an
	}

	if (!g_keys->keyDown['B'])					// wurde 'B' losgelassen?
		bp=FALSE;						// Setze bp auf False

	if ((g_keys->keyDown ['E']) && !ep)				// wurde 'E' gedrückt und wird nicht mehr gehalten?
	{
		ep=TRUE;						// Setze ep auf True
		env=!env;						// wechsel Environment Mapping aus/an
	}

	if (!g_keys->keyDown['E'])					// wurde 'E' losgelassen?
		ep=FALSE;						// Setze ep auf False

	angle += (float)(milliseconds) / 60.0f;				// aktualisiere angle basierend auf dem Timer

Im ursprünglichen Tutorial wurden alle AVI Dateien mit der selben Geschwindigkeit abgespielt. Danach wurde das Tutorial neu geschrieben, um das Video mit der korrekten Geschwindigkeit abzuspielen. next wird um die Anzahl der Millisekunden inkrementiert, die vergangen sind, seitdem dieser Codeabschnitt das letzte Mal aufgerufen wurde. Wenn Sie sich an vorher erinnern, da haben wir berechnet, wie lange jeder Frame in Millisekunden (mpf) angezeigt werden soll. Um den aktuellen Frame zu berechnen, nehmen wir die Menge der Zeit die verstrichen ist (next) und dividieren sie durch die Zeit, die jeder Frame angezeigt werden soll (mpf).

Danach überprüfen wir, dass der aktuelle Frame der Animation nicht über den letzten des Videos geht. Falls doch, wird frame zurück auf null gesetzt, der Animations Timer (next) wird auf 0 gesetzt und die Animation fängt von vorne an.

Der folgende Code wird Frames überspringen, wenn ihr Computer zu langsam ist oder eine andere Applikation ihre CPU beschäftigt. Wenn Sie jeden Frame angezeigt haben wollen, egal wie langsam der Computer des Benutzers ist, können Sie überprüfen, ob next größer als mpf ist und falls ja, würden Sie next auf 0 setzen und frame um 1 erhöhen. Beide Wege funktionieren, obwohl der folgende Code besser für schnellere Maschinen ist.

Wenn Sie genügend motiviert sind, können Sie versuchen Zurückspulen, schnelles Vorspulen, Pause oder rückwärts abspielen, zu implementieren.

	next+= milliseconds;						// inkrementiere next basierend auf dem Timer (Milliseconds)
	frame=next/mpf;							// berechne den aktuellen Frame

	if (frame>=lastframe)						// Haben wir den letzten Frame hinter uns?
	{
		frame=0;						// Resette den Frame zurück auf null (anfang des Videos)
		next=0;							// Resette den Animations Timer (next)
	}
}

Nun zum Zeichnen-Code :) Wir löschen den Screen und den Depth Buffer. Dann holen wir ein Frame aus der Animation. Erneut sei gesagt, ich habe versucht es einfach zu halten! Sie übergeben den ausgelesenen Frame (frame) an GrabAVIFrame(). Ziemlich einfach! Natürlich müssten Sie, wenn Sie mehrere AVI's hätten eine Textur ID mit übergeben. (Das müssten Sie selber machen).

void Draw (void)							// Zeichne unsere Szene
{
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// lösche Screen und Depth Buffer

	GrabAVIFrame(frame);						// Hole ein Frame aus dem AVI
 

Der folgende Code überprüft, ob wir ein Hintergrund-Bild zeichnen wollen. Wenn bg gleich TRUE ist, resetten wir die Modelview Matrix und zeichnen einen einzelnen texturierten Quad (texturiert mit einem Frame aus dem AVI Video), groß genug, um den gesamten Screen auszufüllen. Der Quad wird 20 Einheiten in den Screen hinein gezeichnet, weshalb er hinter dem Objekt erscheint (weiter weg also).

	if (bg)								// Ist der Hintergrund sichtbar?
	{
		glLoadIdentity();					// Resette die Modelview Matrix
		glBegin(GL_QUADS);					// fange an den Hintergrund zu zeichnen (ein Quad)
			// vordere Seite
			glTexCoord2f(1.0f, 1.0f); glVertex3f( 11.0f,  8.3f, -20.0f);
			glTexCoord2f(0.0f, 1.0f); glVertex3f(-11.0f,  8.3f, -20.0f);
			glTexCoord2f(0.0f, 0.0f); glVertex3f(-11.0f, -8.3f, -20.0f);
			glTexCoord2f(1.0f, 0.0f); glVertex3f( 11.0f, -8.3f, -20.0f);
		glEnd();						// fertig mit dem Zeichnen des Hintergrunds
	}

Nachdem der Hintergrund gezeichnet wurde (oder auch nicht), resetten wir die Modelview Matrix (was uns wieder zurück zum Mittelpunkt des Screens bringt). Wir translatieren dann 10 Einheiten in den Screen hinein.

Danach überprüfen wir, ob env gleich TRUE ist. Wenn dem so ist, aktivieren wir Sphere Mapping, um den Environment Mapping Effekt zu erzeugen.

	glLoadIdentity ();						// Resette die Modelview Matrix
	glTranslatef (0.0f, 0.0f, -10.0f);				// Translatiere 10 Einheiten in den Screen hinein

	if (env)							// Ist Environment Mapping an?
	{
		glEnable(GL_TEXTURE_GEN_S);				// aktiviere Texture Coord Generation für S (NEU)
		glEnable(GL_TEXTURE_GEN_T);				// aktiviere Texture Coord Generation für T (NEU)
	}

Ich habe in letzter Minute noch etwas Code hinzugefügt Er rotiert auf der X-Achse und Y-Achse (basierend auf dem Wert von angle) und translatiert dann 2 Einheiten auf der Z-Achse. Das bewegt uns vom Mittelpunkt des Screens weg. Wenn Sie die drei folgenden Codezeilen entfernen, wird das Objekt um den Mittelpunkt des Screens rotiert. Mit diesen drei Zeilen Code, bewegt sich das Objekt etwas umher, wenn es rotiert :)

Wenn Sie Rotationen und Translationen nicht verstehen... sollten Sie nicht dieses Tutorial lesen :)

	glRotatef(angle*2.3f,1.0f,0.0f,0.0f);				// streue etwas Rotation ein, um die Dinge etwas umher zu bewegen
	glRotatef(angle*1.8f,0.0f,1.0f,0.0f);				// streue etwas Rotation ein, um die Dinge etwas umher zu bewegen
	glTranslatef(0.0f,0.0f,2.0f);					// Nach der Rotation translatiere zur neuen Position

Der folgende Code überprüft, welcher Effekt (Objekt) wir zeichnen wollen. Wenn der Wert von effect gleich 0 ist, machen wir ein paar Rotationen und zeichnen einen Würfel. Die Rotationen lassen den Würfel auf der X-Achse, Y-Achse und Z-Achse rotieren. Mittlerweile sollten Sie den Code einen Würfel zu erzeugen, in Ihr Gehirn eingebrannt haben :)

	switch (effect)							// Welcher Effekt?
	{
	case 0:								// Effekt 0 - Würfel
		glRotatef (angle*1.3f, 1.0f, 0.0f, 0.0f);		// Rotiere auf der X-Achse um angle
		glRotatef (angle*1.1f, 0.0f, 1.0f, 0.0f);		// Rotiere auf der Y-Achse um angle
		glRotatef (angle*1.2f, 0.0f, 0.0f, 1.0f);		// Rotiere auf der Z-Achse um angle
		glBegin(GL_QUADS);					// fange an einen Würfel zu zeichnen
			// vordere Seite
			glNormal3f( 0.0f, 0.0f, 0.5f);
			glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
			glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
			glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
			glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
			// hintere Seite
			glNormal3f( 0.0f, 0.0f,-0.5f);
			glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
			glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
			glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
			glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
			// obere Seite
			glNormal3f( 0.0f, 0.5f, 0.0f);
			glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
			glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
			glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
			glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
			// untere Seite
			glNormal3f( 0.0f,-0.5f, 0.0f);
			glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
			glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
			glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
			glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
			// rechte Seite
			glNormal3f( 0.5f, 0.0f, 0.0f);
			glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
			glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
			glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
			glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
			// linke Seite
			glNormal3f(-0.5f, 0.0f, 0.0f);
			glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
			glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
			glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
			glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
		glEnd();						// fertig mit dem Zeichnen des Würfels
		break;							// Fertig mit Effekt 0

Hier zeichnen wir die Sphere. Wir fangen mit ein paar Rotationen auf der X-Achse, Y-Achse und Z-Achse an. Dann zeichnen wir die Sphere. Die Spehere wird einen Radius von 1.3f haben, mit 20 Scheiben und 20 Stacks. Ich habe 20 gewählt, da ich die Sphere nicht perfekt glatt haben wollte. Weniger Scheiben und Stacks lassen die Sphere rauher aussehen (weniger weich), was es hablwegs offensichtlich macht, dass die Sphere rotiert, wenn Sphere Mapping aktiviert ist. Versuchen Sie ein wenig mit den Werten herum zu spielen! Es ist noch wichtig anzumerken, dass mehr Scheiben und Stacks mehr Rechenleistung benötigen!

	case 1:								// Effekt 1 - Sphere
		glRotatef (angle*1.3f, 1.0f, 0.0f, 0.0f);		// Rotiere auf der X-Achse um angle
		glRotatef (angle*1.1f, 0.0f, 1.0f, 0.0f);		// Rotiere auf der Y-Achse um angle
		glRotatef (angle*1.2f, 0.0f, 0.0f, 1.0f);		// Rotiere auf der Z-Achse um angle
		gluSphere(quadratic,1.3f,20,20);			// zeichne eine Sphere
		break;							// fertig mit dem Zeichnen der Sphere

Hier zeichnen wir den Zylinder. Wir fangen mit ein paar einfachen Roatationen auf der X-Achse, Y-Achse und Z-Achse an. Unser Zylinder hat oben und unten einen Radius von 1.0f Einheiten. Er ist 3.0f Einheiten hoch und besteht aus 32 Scheiben und 32 Stacks. Wenn Sie die Scheiben und Stacks reduzieren, wird der Zylinder aus weniger Polygonen bestehen und weniger rund aussehen.

Bevor wir den Zylinder zeichnen, translatieren wir -1,5f Einheiten auf der Z-Achse. Dadurch wird unser Zylinder um seinen eigenen Mittelpunkt rotieren. Die allgemeingültige Regel einen Zylinder zu zentrieren ist, seine Höhe durch 2 zu dividieren und um Ergebniss in die negative Richtung auf der Z-Achse zu tranlsatieren. Wenn Sie keine Ahnung haben, wovon ich spreche, dann nehmen Sie die folgende tranlatef(...) Zeile heraus. Der Zylinder wird um seine Basis anstatt um seinen Mittelpunkt rotieren.

	case 2:								// Effekt 2 - Zylinder
		glRotatef (angle*1.3f, 1.0f, 0.0f, 0.0f);		// Rotiere auf der X-Achse  um angle
		glRotatef (angle*1.1f, 0.0f, 1.0f, 0.0f);		// Rotiere auf der Y-Achse um angle
		glRotatef (angle*1.2f, 0.0f, 0.0f, 1.0f);		// Rotiere auf der Z-chse um angle
		glTranslatef(0.0f,0.0f,-1.5f);				// zentriere den Zylinder
		gluCylinder(quadratic,1.0f,1.0f,3.0f,32,32);		// zeichne einen Zylinder
		break;							// fertig mit dem Zeichnen des Zylinders
	}

Als nächstes überprüfen wir, ob env gleich TRUE ist. Falls ja, deaktivieren wir Sphere Mapping. Wir rufen glFlush() auf, um unsere Pipeline zu flushen (dadurch stellen wir sicher, dass alles gerendert wird, bevor wir den nächsten Frame zeichnen).

	if (env)							// Environment Mapping aktiviert?
	{
		glDisable(GL_TEXTURE_GEN_S);				// deaktiviere Texturkoordinaten Erzeugung für S (Neu)
		glDisable(GL_TEXTURE_GEN_T);				// deaktiviere Texturkoordinaten Erzeugung für T (Neu)
	}

	glFlush ();							// Flushe die GL Rendering Pipeline
}

Ich hoffe Sie haben dieses Tutorial genossen. Es ist 2:00am zur Zeit... Ich habe für die letzten 6 Stunden an diesem Tutorial gearbeitet. Es klingt zwar verrückt, aber Dinge so niederzuschreiben, dass sie auch tatsächlich Sinn machen, ist gar keine so leichte Aufgabe. Ich habe das Tutorial 3 Mal nun gelesen und ich versuche immer noch, einige Dinger verständlicher zu machen. Glauben Sie es oder auch nicht, es ist mir wichtig, dass Sie verstehen, wie die Dinge funktionieren und warum sie funktionieren. Darum schwafel ich auch so, habe endlos viele Kommentar, etc.

Wie dem auch sei... Ich würde gerne etwas Feedback über dieses Tutorial bekommen. Wenn Sie Fehler finden oder mir helfen wollen, das Tutorial besser zu machen, kontaktieren Sie mich bitte. Wie ich bereits erwähnte, das ist mein erster Versuch mit AVI. Normelweise würde ich nicht gleich ein Tutorial über ein Thema schreiben, dass ich gerade erst gelernt habe, aber ich war so aufgeregt und außerdem ärgerte es mich, dass es es so wenige Informationen über dieses Thema gibt. Ich hoffe, dass ich die Tür geöffnet habe, um eine Flut von höher-qualitativen AVI-Demos und Beispiel-Code zu bekommen. Kann passieren... vielleicht auch nicht. Wie dem auch sei, diesen Code können Sie verwenden wie sie möchten!

Großen Dank an Fredster für die Face-AVI Datei. Face ist eine von 6 AVI Animationen, die er mir für dieses Tutorial geschickt hat. Keine Fragen gestellt, keine Bedingungen. Ich habe ihn angemailt und er hat mir einfach geholfen... Großen Respekt!

Noch größerer Dank geht an Jonathan de Blok. Wenn er nicht wäre, würde dieses Tutorial nicht existieren. Er hat mein Interesse am AVI-Format geweckt, indem er mir Teile seines Code für seinen eigenen AVI-Player schickte. Er hat mir auch sehr geholfen, indem er mir jede Frage beantwortet hat, die ich bezüglich seines Codes hatte. Es ist wichtig anzumerken, dass nichts an seinen Code angelehnt ist oder daraus genommen wurde, er wurde nur dazu verwendet, um zu verstehen, wie ein AVI-Player funktioniert. Mein Player öffnet, dekodiert und spielt AVI-Datei mit völlig anderem Code!

Danke an jeden für die großartige Unterstützung! Diese Seite wäre nichts ohne seine Besucher!!!